package com.uxsino.simo.collector.connections;

import java.net.UnknownHostException;
import java.util.Map;

import org.jinterop.dcom.common.JIException;
import org.jinterop.dcom.common.JISystem;
import org.jinterop.dcom.core.IJIComObject;
import org.jinterop.dcom.core.JIArray;
import org.jinterop.dcom.core.JIComServer;
import org.jinterop.dcom.core.JIProgId;
import org.jinterop.dcom.core.JISession;
import org.jinterop.dcom.core.JIString;
import org.jinterop.dcom.core.JIVariant;
import org.jinterop.dcom.impls.JIObjectFactory;
import org.jinterop.dcom.impls.automation.IJIDispatch;
import org.jinterop.dcom.impls.automation.IJIEnumVariant;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.uxsino.simo.connections.AbstractConnection;
import com.uxsino.simo.connections.exception.SimoConnectionException;
import com.uxsino.simo.connections.target.WMITarget;

public class WMIIISConnection extends AbstractConnection<WMITarget> {

    private Logger logger = LoggerFactory.getLogger(WMIIISConnection.class);

    private IJIDispatch wbemService;

    private JISession session;

    private IJIDispatch dispatch;

    private WMITarget target;

    private static final String WMI_PROGID = "WbemScripting.SWbemLocator";

    @Override
    public int connect(WMITarget target) {
        super.connect(target);
        connected = false;
        state = 0;
        this.target = target;
        session = JISession.createSession(target.getDomain(), target.getUsername(), target.getPassword());
        session.useSessionSecurity(false);
        JIComServer comServer = null;

        try {

            JISystem.setAutoRegisteration(true);
            comServer = new JIComServer(JIProgId.valueOf(WMI_PROGID), target.host, session);

        } catch (UnknownHostException | JIException e) {
            logger.error("create JIComServer error: ", e);
            return state;
        }

        try {
            IJIComObject excelObject = comServer.createInstance();
            dispatch = (IJIDispatch) JIObjectFactory.narrowObject(excelObject.queryInterface(IJIDispatch.IID));

        } catch (Exception e) {
            logger.error(": ", e);
        }

        connected = true;       // not really connected
        state = 1;
        return state;
    }

    @Override
    public Object execCmd(Object cmdPattern) throws SimoConnectionException {
        JIVariant resultsV[];
        String[] cmdArr = getCmd(cmdPattern);

        try {
            resultsV = dispatch.callMethodA("ConnectServer",
                new Object[] { new JIString(target.host), new JIVariant(cmdArr[0]), JIVariant.OPTIONAL_PARAM(),
                        JIVariant.OPTIONAL_PARAM(), JIVariant.OPTIONAL_PARAM(), JIVariant.OPTIONAL_PARAM(), 0,
                        JIVariant.OPTIONAL_PARAM() });
        } catch (JIException e) {
            logger.error("error at dispatch callMethodA: ", e);
            throw new SimoConnectionException(e);
        }

        try {
            wbemService = (IJIDispatch) JIObjectFactory.narrowObject((resultsV[0]).getObjectAsComObject());
        } catch (JIException e) {
            logger.error("error at creating webService: ", e);
            throw new SimoConnectionException(e);
        }
        logger.info("WMI connected!");

        JSONArray wql_results = new JSONArray();
        try {
            JIVariant[] results = wbemService.callMethodA("ExecQuery", new Object[] { new JIString(cmdArr[1]),
                    JIVariant.OPTIONAL_PARAM(), JIVariant.OPTIONAL_PARAM(), JIVariant.OPTIONAL_PARAM() });

            IJIDispatch wOSd = (IJIDispatch) JIObjectFactory.narrowObject((results[0]).getObjectAsComObject());

            IJIComObject enumComObject = wOSd.get("_NewEnum").getObjectAsComObject();
            IJIEnumVariant enumVariant = (IJIEnumVariant) JIObjectFactory
                .narrowObject(enumComObject.queryInterface(IJIEnumVariant.IID));
            JIVariant countVar = wOSd.get("Count"); // error occur here may due to wrong namespace
            int count = countVar.getObjectAsInt();

            IJIDispatch wbemObject_dispatch = null;
            for (int i = 0; i < count; i++) {
                Object[] values = enumVariant.next(1);
                JIArray array = (JIArray) values[0];
                Object[] arrayObj = (Object[]) array.getArrayInstance();
                for (int j = 0; j < arrayObj.length; j++) {
                    wbemObject_dispatch = (IJIDispatch) JIObjectFactory
                        .narrowObject(((JIVariant) arrayObj[j]).getObjectAsComObject());
                }
                JIVariant[] wmi_results = wbemObject_dispatch.callMethodA("GetObjectText_", new Object[] { 1 });
                for (JIVariant wmi_result : wmi_results) {
                    String str = wmi_result.getObjectAsString2();
                    wql_results.add(getData(str.toCharArray(), 0, str.length()));
                }
            }

            logger.debug("执行WQL查询： 查询成功!");
        } catch (JIException e) {
            logger.error("执行WQL查询:查询失败!", e);
            throw new SimoConnectionException(e);
        }
        // System.out.println(wql_results);
        return wql_results;
    }

    @Override
    public Object buildCmd(String cmdPattern, Map<String, String> args) {
        return cmdPattern;
    }

    @Override
    public int close() {
        try {
            JISession.destroySession(session);
        } catch (JIException e) {
            logger.error("close session error:", e);
        }
        connected = false;
        super.close();
        return 0;
    }

    private String[] getCmd(Object cmd) {
        String cmdStr = (String) cmd;
        String[] res = cmdStr.split("###");
        return res;
    }

    // str range:[index, endIndex)
    private JSONObject getData(char[] str, int index, int endIndex) {
        JSONObject result = new JSONObject();
        index = advIndex(index, endIndex, str);
        while (index < endIndex) {
            index = advIndex(index, endIndex, str);
            if (index == endIndex) {
                break;
            }

            if (index + 11 <= endIndex && String.valueOf(str, index, 11).equals("instance of")) {
                int leftPIndex = findLeftP(index, endIndex, str);
                index += 11;
                String key = String.valueOf(str, index, leftPIndex - index).trim();
                index = leftPIndex + 1;
                int leftCount = 1;
                int i = index;
                while (i < endIndex && leftCount > 0) {
                    char c = str[i];
                    if (c == '{') {
                        leftCount++;
                    } else if (c == '}') {
                        leftCount--;
                    }
                    i++;
                }
                // in well formed input string index==endIndex is not possible
                JSONObject value = getData(str, index, i - 1);
                result.put(key, value);
                index = i;
            } else {
                int rightIndex = index;
                while (rightIndex < endIndex && str[rightIndex] != '=') {
                    rightIndex++;
                }
                String key = String.valueOf(str, index, rightIndex - index).trim();
                index = rightIndex + 1;
                index = advIndex(index, endIndex, str);
                if (index + 11 <= endIndex && String.valueOf(str, index, 11).equals("instance of")) {
                    int i = findLeftP(index + 11, endIndex, str) + 1;
                    int leftCount = 1;
                    i++;
                    while (i < endIndex && leftCount > 0) {
                        if (str[i] == '{')
                            leftCount++;
                        else if (str[i] == '}')
                            leftCount--;
                        i++;
                    }
                    JSONObject value = getData(str, index, i - 1);
                    index = i;
                    result.put(key, value);
                } else {
                    int i = index;
                    while (i < endIndex && str[i] != ';') {
                        i++;
                    }
                    String value = String.valueOf(str, index, i - index).trim();
                    if (value.length() > 0 && value.charAt(0) == '"' && value.charAt(value.length() - 1) == '"') {
                        value = value.substring(1, value.length() - 1);
                    }
                    if (key.equals("Directory")) {
                        value = getDirectory(value);
                    }
                    index = i;
                    result.put(key, value);
                }
            }
            index = advIndex(index, endIndex, str);
            if (str[index] == ';') {
                index++;
                index = advIndex(index, endIndex, str);
            }
        }
        return result;
    }

    private int advIndex(int index, int endIndex, char[] str) {
        while (index < endIndex
                && (str[index] == '\n' || str[index] == '\r' || str[index] == '\t' || str[index] == ' ')) {
            index++;
        }
        return index;
    }

    // find the first position of '{', search start from start
    private int findLeftP(int start, int endIndex, char[] str) {
        while (start < endIndex && str[start] != '{') {
            start++;
        }

        return start;
    }

    // convert directory "%SystemDrive%\\\\inetpub\\\\logs\\\\FailedReqLogFiles"
    // to %SystemDrive%\\inetpub\\logs\\FailedReqLogFiles
    private String getDirectory(String dir) {
        String space = dir.replaceAll("\\\\\\\\", " ");
        char[] arr = space.toCharArray();
        for (int i = 0; i < arr.length; i++) {
            if (arr[i] == ' ') {
                arr[i] = 92;
            }
        }
        return String.valueOf(arr);
    }

}
